#include "graphics/utils.h"
#include "graphics/image.h"
#include "graphics/framebuffer_object.h"
#include "graphics/program_glsl.h"
#include "graphics/cube_texture_gl.h"
#include "graphics/image_texture_gl.h"
#include "graphics/error_gl.h"
#include "graphics/render_device_fbo.h"
#include "tools/obj_loader.h"
#include "graphics/shader_object.h"
#include "shapes/triangle_mesh.h"
#include "aop_demo.h"
#include "nvModel/nvUtils.h"

namespace venus {
AopDemo::AopDemo() 
    : GlutWindow("Aop Demo",10, 10, 512, 512, GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH),
      camera_(Point(-0.268, 0, 0.255)), light_(Point(-0.268, 0, 0.255)) {
  camera_.set_min_dist(0.1);
  camera_.set_fovy(30.0);
  camera_.set_velocity(0, 0, 0.05);
  light_.set_fovy(30.0);
  Utils::SetNULL(model_);
  Utils::SetNULL(min_max_program_);
  Utils::SetNULL(peel_program_);
  Utils::SetNULL(to_bits_program_);
  Utils::SetNULL(visualize_program_);
  Utils::SetNULL(render_device_);
  Utils::SetNULL(mesh_);
  Utils::SetNULL(model_gl_);
  for (int i = 0; i < 8; ++i) {
    mrt_[i] = GL_COLOR_ATTACHMENT0_EXT+i;
  }
}
AopDemo::~AopDemo() {
  Utils::Delete(model_);
  Utils::Delete(min_max_program_);
  Utils::Delete(peel_program_);
  Utils::Delete(to_bits_program_);
  Utils::Delete(visualize_program_);
  Utils::Delete(render_device_);
  Utils::Delete(mesh_);
  Utils::Delete(model_gl_);
}
bool AopDemo::Initialize() {
  glewInit();
  if (!glewIsSupported( "GL_VERSION_2_0 GL_EXT_framebuffer_object")) {
    printf( "Required extensions not supported.\n");
    return false;
  }
  //model_ = new nv::Model;
  //model_->loadModelFromFile("../models/dragon.obj");
  //model_->removeDegeneratePrims();
  //model_->computeNormals();
  //model_->compileModel(nv::Model::eptTriangles);
  //printf("triangle count: %d\n", 
  //       model_->getCompiledIndexCount(nv::Model::eptTriangles) / 3);
  this->LoadShaders();
  render_device_ = new RenderDeviceFBO;
  this->InitPeelingRenderTargets();
  mesh_ = LoadObj("../models/dragon.obj");
  mesh_->Scale2Size(0.2);
  mesh_->Translate2Pos(Point(0));
  model_gl_ = new ModelGL;
  model_gl_->SetData(mesh_);
  //glDisable(GL_DEPTH_TEST);
  quad_display_list_ = MakeFullScreenQuad();
  return true;
}

void AopDemo::OnDisplay() {
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  //light_.ApplyProjectionTransform();
  light_.ApplyViewTransform();
  this->RenderDepthPeeling();
  this->TransformToBits();
  //camera_.ApplyProjectionTransform();
  //camera_.ApplyViewTransform();
  this->Visualize();
  DisplayStastics("frame rate", fps_counter_.Update(), "fps");
  glutSwapBuffers();
  CheckErrorsGL("DisplayEnd");
}

void AopDemo::RenderDepthPeeling() {
  //glDisable(GL_CULL_FACE);
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  render_device_->Activate();
  render_device_->SetRenderTarget(min_max_depth_tex_);  
  render_device_->DisableDepthBuffer();
  glDrawBuffers(1, &mrt_[0]);
  render_device_->Check();
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  glBlendEquationEXT(GL_MAX_EXT);

  min_max_program_->Begin();  
  //nv::DrawTriangles(model_);
  model_gl_->DrawBound();
  CheckErrorsGL("min-max");
  /*min_max_depth_tex_->Save("min-max.jpg");*/

  render_device_->SetRenderTargets(8, depth_texs_);
  glDrawBuffers(8, mrt_);  
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT); 
  peel_program_->Begin();

  GLint min_max_depth_var = peel_program_->GetUniformVar("MinMaxDepthTex");
  glActiveTexture(GL_TEXTURE0);
  min_max_depth_tex_->Bind();  
  glUniform1i(min_max_depth_var, 0);
  //nv::DrawTriangles(model_);
  model_gl_->Draw();
  render_device_->Deactivate();
  CheckErrorsGL("peel");
 /* depth_texs_[0]->Save("tex0.jpg");
  depth_texs_[1]->Save("tex1.jpg");
  depth_texs_[2]->Save("tex2.jpg");*/
  //depth_texs_[3]->Save("tex3.jpg");
  //depth_texs_[4]->Save("tex4.jpg");
  //depth_texs_[5]->Save("tex5.jpg");
  //depth_texs_[6]->Save("tex6.jpg");
  //depth_texs_[7]->Save("tex7.jpg");

  glDisable(GL_BLEND); 
  ProgramGLSL::Disable();
}
void AopDemo::TransformToBits() {
  CheckErrorsGL("Transform Begin");
  render_device_->Activate();
  render_device_->SetRenderTarget(bits_tex_);
  render_device_->Check();
  //this is important, otherwise, it prompts error
  glDrawBuffers(1, mrt_);
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  //set textures
  to_bits_program_->Begin();

  const char *tex_names[] = { "Tex0", "Tex1", "Tex2", "Tex3", "Tex4", "Tex5", 
    "Tex6", "Tex7" };
  GLint tex_vars[8];
  for (int i = 0; i < 8; ++i) {
    tex_vars[i] = to_bits_program_->GetUniformVar(tex_names[i]);
    glActiveTexture(GL_TEXTURE0+i);
    depth_texs_[i]->Bind();
    glUniform1i(tex_vars[i], i);
  }   
  GLint min_max_var = to_bits_program_->GetUniformVar("MinMaxDepthTex");
  glActiveTexture(GL_TEXTURE8);
  min_max_depth_tex_->Bind();
  glUniform1i(min_max_var, 8);
  //render quad
  glCallList(quad_display_list_);
  to_bits_program_->End();
  
  render_device_->Deactivate();
  CheckErrorsGL("Transform End");
}
void AopDemo::Visualize() {
  CheckErrorsGL("Visualize begin");
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat mat_shininess[] = { 50.0 };
  GLfloat mat_diffuse[] = { 0., 0., 1.0, 1.0 };
  Point pos = camera_.position();
  GLfloat light_position[] = { pos.x, pos.y, pos.z, 0.0 };

//  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
 // glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_FLAT);
  bits_tex_->Enable();
  bits_tex_->Bind();
  int width, height;
  glGetTexLevelParameteriv(bits_tex_->target(), 0, GL_TEXTURE_WIDTH, &width);
  glGetTexLevelParameteriv(bits_tex_->target(), 0, GL_TEXTURE_HEIGHT, &height);
  unsigned int* data = new unsigned int[4 * width * height];
  CheckErrorsGL("test");
  glGetTexImage(bits_tex_->target(), 0, GL_RGBA_INTEGER, GL_UNSIGNED_INT, data);
  //bits_tex_->Unbind();
  bits_tex_->Disable();
  // draw cubes accordingly
  int index = 0;
  float step = 1.f / 512.f;
  for (int i = 0; i < height; ++i) {
    for (int j = 0; j < width; ++j) {
      unsigned int *p = &data[index];
      //process bit by bit
      for (int c = 0; c < 4; ++c) {
        unsigned int sign = 1;
        for (int b = 0; b < 32; ++b) {
          if (p[c] & sign) {
            //draw cube
            glPushMatrix();
            glTranslatef(-1.0 + j * step, -1.0 + i * step, -1.0 + (c * 32 + b) * step);
            glutSolidCube(0.001);
            glPopMatrix();
          }
          sign <<= 1;
        }
        /*if (p[c]) {
          glutSolidCube(0.1);
        }*/
      }
    }
    index += 4;
  }
  delete[] data;
  //glutSolidSphere(0.1, 32, 32);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHT0);
  glDisable(GL_LIGHTING);
  //static int layer = 0;
  //Info("layer = %d", layer);
  //glClear(GL_COLOR_BUFFER_BIT);
  //visualize_program_->Begin();
  //GLint layer_var = visualize_program_->GetUniformVar("layer");
  //GLint bits_tex_var = visualize_program_->GetUniformVar("BitsTex");
  //glUniform1i(layer_var, layer);
  //glUniform1i(bits_tex_var, 0);
  //glActiveTexture(GL_TEXTURE0);
  //bits_tex_->Bind();
  //glCallList(quad_display_list_);
  //visualize_program_->End();
  //++layer;
  //if (layer == 128) layer = 0;
  CheckErrorsGL("Visualize end");
}

void AopDemo::OnResize(int width, int height) {
  printf("-------\n");
  if (width != this->width() || height != this->height()) {
    this->set_width(width);
    this->set_height(height);
    this->DeletePeelingRenderTargets();
    this->InitPeelingRenderTargets();
  }
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  camera_.set_aspect_ratio(static_cast<double>(width)/height);
  camera_.ApplyProjectionTransform();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}
void AopDemo::OnIdle() {
  this->ReDraw();
}
void AopDemo::OnKeyboard(unsigned char key, int x, int y) {
  switch (key) {
    case 'a':
      camera_.Yaw(-1);
      break;
    case 'd':
      camera_.Yaw(1);
      break;
    case 'w':
      camera_.Walk(1);
      break;
    case 's':
      camera_.Walk(-1);
      break;
    case 'q':
      camera_.Pitch(1);
      break;
    case 'z':
      camera_.Pitch(-1);
      break;
    case 'n':
      break;
  }
}

void AopDemo::OnSpecialKey(int key, int x, int y) {
  switch(key) {
  case GLUT_KEY_UP:
    camera_.Fly(1);
    break;
  case GLUT_KEY_DOWN:
    camera_.Fly(-1);
    break;
  case GLUT_KEY_LEFT:
    camera_.Strafe(-1);
    break;
  case GLUT_KEY_RIGHT:
    camera_.Strafe(1);
    break;
  default:
    break;
  }
}

void AopDemo::ReleaseResources() {
}

void AopDemo::LoadShaders() {
  min_max_program_ = new ProgramGLSL;
  peel_program_ = new ProgramGLSL;
  to_bits_program_ = new ProgramGLSL;
  visualize_program_ = new ProgramGLSL;
  min_max_program_->Load("shaders/min_max_depth_vertex.glsl",
                         "shaders/min_max_depth_fragment.glsl");
  min_max_program_->Link();
  peel_program_->Load("shaders/bucket_peeling_vertex.glsl",
                      "shaders/bucket_peeling_fragment.glsl");
  peel_program_->Link();
  to_bits_program_->Load("shaders/to_bits_vertex.glsl",
                         "shaders/to_bits_fragment.glsl");
  to_bits_program_->Link();
  visualize_program_->Load("shaders/visualize_vertex.glsl",
                           "shaders/visualize_fragment.glsl");
  visualize_program_->Link();
}
bool AopDemo::InitPeelingRenderTargets() {
  Info("init mrt");
  for (int i = 0; i < 8; ++i) {
    depth_texs_[i] = new ImageTextureGL(GL_TEXTURE_RECTANGLE);
    V_RET(depth_texs_[i]->Initialize(
        this->width(), this->height(), GL_RGBA32F));
  }
  Info("init min-max");
  min_max_depth_tex_ = new ImageTextureGL(GL_TEXTURE_RECTANGLE);
  V_RET(min_max_depth_tex_->Initialize(
      this->width(), this->height(), GL_RG32F));
  Info("init bits tex");
  bits_tex_ = new ImageTextureGL(GL_TEXTURE_RECTANGLE);
  V_RET(bits_tex_->Initialize(this->width(), this->height(), GL_RGBA32UI));
  if (CheckErrorsGL("InitBucketPeelingRenderTargets")) return false;
  return true;
}
void AopDemo::DeletePeelingRenderTargets() {
  Utils::Delete(bits_tex_);
  Utils::Delete(min_max_depth_tex_);
  Utils::Delete(depth_texs_, 8);
}
} // namespace venus
